package online.heycm.platform.lock;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.MessageFormat;

/**
 * 分布式锁切面，环绕通知上锁/解锁
 *
 * @author heycm
 * @since 2023/1/27 15:45
 */
@Aspect
// @Component 通过@EnableDistributedLock加载
public class DistributedLockAop {

    private static final String LOCK_KEY = "distributed:lock:{0}:{1}";

    @Autowired
    private RedisLock redisLock;

    @Pointcut("@annotation(online.heycm.platform.lock.DistributedLock)")
    public void cut() {
    }

    @Around("cut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        final MethodSignature signature = (MethodSignature) pjp.getSignature();
        final Method method = signature.getMethod();
        final DistributedLock lock = method.getAnnotation(DistributedLock.class);
        try {
            this.lock(lock, method, pjp.getArgs());
            return pjp.proceed();
        } finally {
            this.unlock(lock);
        }
    }

    private void lock(DistributedLock lock, Method method, Object[] args) throws IllegalAccessException {
        if (lock == null) {
            return;
        }
        String key = getLockKey(method, args);
        key = MessageFormat.format(LOCK_KEY, lock.prefix(), key);
        boolean ok = redisLock.lock(key, lock.waitTime(), lock.leaseTime());
        if (!ok) {
            throw new DistributedLockException(String.format("获取锁[%s]失败", key));
        }
    }

    private void unlock(DistributedLock lock) {
        if (lock == null) {
            return;
        }
        redisLock.unlock();
    }

    private String getLockKey(Method method, Object[] args) throws IllegalAccessException {
        // 获取目标方法参数上@DistributedLockKey注解，将其值作为key
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                if (annotation instanceof DistributedLockKey) {
                    return args[i].toString();
                }
            }
        }
        // 获取目标方法参数属性上@DistributedLockKey注解，将其值作为key
        for (Object arg : args) {
            for (Field field : arg.getClass().getDeclaredFields()) {
                DistributedLockKey annotation = field.getAnnotation(DistributedLockKey.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    return field.get(arg).toString();
                }
            }
        }
        throw new IllegalArgumentException("没有找到@DistributedLockKey注解");
    }
}
